package com.fsq.portscan;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import com.fsq.portscan.Utils.DbUtils;
import com.fsq.portscan.model.Row;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.Args;
import org.apache.http.util.ByteArrayBuffer;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;

public class ScanThread extends Task<String> {

    private TreeItem<String> treeitemip;
    private Integer port;
    private ObservableList<Row> items;
    private Integer to;
    private Label id_status;
    private TreeItem<String> treeItem;

    public ScanThread(TreeItem<String> treeitem, Integer port, ObservableList<Row> items, Integer to, Label id_status,
                      TreeItem<String> treeItem) {
        super();
        this.treeitemip = treeitem;
        this.port = port;
        this.items = items;
        this.to = to;
        this.id_status = id_status;
        this.treeItem = treeItem;

    }

    @Override
    protected String call() throws Exception {

        Socket s = new Socket();
        String ip = treeitemip.getValue();
        String address = ip + ":" + port;
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                id_status.setText(address);
            }
        });
        String banner = null;
        String realIp = null;
        try {
            InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, port);
            realIp = inetSocketAddress.getAddress().getHostAddress();
            s.connect(inetSocketAddress, to);

            s.setSoTimeout(to);

        } catch (IOException e) {
            // System.err.println("端口未开放");
            // e.printStackTrace();
            return null;
        }

        final ByteArrayBuffer buffer = new ByteArrayBuffer(4096);
        try {
            // 读取banner信息
            InputStream instream = s.getInputStream();

            final byte[] tmp = new byte[1024];
            int i = 4;
            int l;
            while (i > 0) {
                if ((l = instream.read(tmp)) != -1) {
                    buffer.append(tmp, 0, l);
                    i--;
                } else {
                    break;
                }
            }
            s.close();

        } catch (Exception e) {
            // e.printStackTrace();
        }
        banner = new String(buffer.toByteArray());
        // 如果端口开放则继续执行下边代码
        SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(TrustAllStrategy.INSTANCE).build();
        HostnameVerifier hostnameVerifier = new MyHostnameVerifier();
        CloseableHttpClient client = HttpClients.custom().setSSLContext(sslContext)
                .setSSLHostnameVerifier(hostnameVerifier).build();

        CloseableHttpResponse response = null;
        Boolean isSSL = false;
        try {
            // 先进程https访问
//            Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36

            HttpGet httpGet = new HttpGet("https://" + (port == 443 ? ip : address));
            httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36");
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(to).setConnectionRequestTimeout(to)
                    .setSocketTimeout(to).setRedirectsEnabled(true).build();
            httpGet.setConfig(requestConfig);
            response = client.execute(httpGet);
            isSSL = true;

        } catch (Exception e) {
            // SSL握手失败说明不是未使用SSL
            // e.printStackTrace();
            try {
                // 如果不是https则尝试http
                HttpGet httpGet = new HttpGet("http://" + (port == 80 ? ip : address));
                httpGet.setHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36");

                RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(to)
                        .setConnectionRequestTimeout(to).setSocketTimeout(to).setRedirectsEnabled(true).build();
                httpGet.setConfig(requestConfig);
                response = client.execute(httpGet);
                isSSL = false;
            } catch (Exception ee) {
                // e.printStackTrace();
            }
        }

        String serverHeader = null;
        long contentLength = -1;
        int statusCode = 0;
        String title = null;

        if (response != null) {
            StringBuilder sbresponse = new StringBuilder();
            StatusLine statusLine = response.getStatusLine();
            sbresponse.append(statusLine.toString()).append("\\n");
            Header[] headers = response.getAllHeaders();
            for (Header header : headers) {
                sbresponse.append(header.getName() + ":" + header.getValue() + "\\n");
                if (header.getName().equals("Server")) {
                    serverHeader = header.getValue();
                }
                // if(header.getName() "Content-Length"){
            }
            banner = sbresponse.toString();

            HttpEntity entity = response.getEntity();
            contentLength = entity.getContentLength();
            statusCode = statusLine.getStatusCode();
            byte[] resp = toByteArray(entity.getContent(), (int) contentLength);
            Charset charset = null;
            try {
                final ContentType contentType = ContentType.get(entity);
                if (contentType != null) {
                    charset = contentType.getCharset();
                }
            } catch (Exception e) {

            }
            if (charset == null) {
                charset = Charset.forName("utf-8");
                // charset = Charset.forName("ISO-8859-1");
            }

            try {
                String strResp = new String(resp, charset);
                Document doc = Jsoup.parse(strResp);
                title = doc.getElementsByTag("title").text();
            } catch (Exception e) {
                // e.printStackTrace();
            }

            // 关闭
            response.close();
        }
        // 关闭
        client.close();

        TreeItem<String> childItem = new TreeItem<String>(ip + ":" + port);

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                treeitemip.getChildren().add(childItem);
            }
        });

        // 添加到树
            Platform.runLater(new Runnable() {
                @Override
                public void run() {
                    if(!treeItem.getChildren().contains(treeitemip))
                        treeItem.getChildren().add(treeitemip);
                }
            });

        // 插入表格

        try {
            Row row = new Row(address, isSSL, serverHeader, title, statusCode, contentLength, banner,realIp);
            Platform.runLater(new Runnable() {

                @Override
                public void run() {
                    items.add(row);
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }

        // 插入数据库
        String sql = "INSERT INTO PUBLIC.webfinderplus(ADDRESS,SSL,SERVER,TITLE,STATUSCODE,CONTENTLENGTH,RESPONSE,REALIP)VALUES(?, ?, ?, ?, ?, ?, ?,?);";

        System.out.println("Insert data : " + sql);
        Connection conn = DbUtils.getConnection();
        try {
            PreparedStatement presm = conn.prepareStatement(sql);
            presm.setString(1, address);
            presm.setBoolean(2, isSSL);
            presm.setString(3, serverHeader);
            presm.setString(4, title);
            presm.setInt(5, statusCode);
            presm.setLong(6, contentLength);
            presm.setString(7, banner);
            presm.setString(8, realIp);


            presm.executeUpdate();
            // presm.executeQuery(("select * from httpsender;");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        return null;

    }

    public static byte[] toByteArray(InputStream instream, int contentlenth) throws IOException {
        if (instream == null) {
            return null;
        }

        Args.check(contentlenth <= Integer.MAX_VALUE, "HTTP entity too large to be buffered in memory");
        if (contentlenth < 0) {
            contentlenth = 4096;
        }
        final ByteArrayBuffer buffer = new ByteArrayBuffer(contentlenth);
        final byte[] tmp = new byte[4096];
        int l;
        while ((l = instream.read(tmp)) != -1) {
            buffer.append(tmp, 0, l);
        }
        return buffer.toByteArray();

    }

    private static boolean isSslTlsHandshake(byte[] bytes) {
        if (bytes.length < 3) {
            throw new IllegalArgumentException("The parameter bytes must have at least 3 bytes.");
        }
        // Check if ContentType is handshake(22)
        if (bytes[0] == 0x16) {
            // Check if "valid" ProtocolVersion >= SSLv3 (TLSv1, TLSv1.1,SSLv2
            // ...)
            if (bytes[1] >= 0x03 || (bytes[1] == 0x00 && bytes[2] == 0x02)) {
                return true;
            }
        }
        return false;
    }
}
